package cus.area.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cus.area.util.constants.AreaData;
import cus.area.util.entity.AreaEnum;
import cus.area.util.entity.ParseAreaResult;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * 地址解析,只解析省,市,地区,详细地址信息
 */
public class ParseArea {
    private static Map<String, String> provinceShort = new LinkedHashMap<>();

    private static Map<String, String> cityShort = new LinkedHashMap<>();

    private static Map<String, String> countyShort = new LinkedHashMap<>();

    static {
        for (Map.Entry<String, String> entry : AreaData.PROVINCES.entrySet()) {
            String result = entry.getValue();
            for (String key : AreaData.provinceKeys) {
                result = result.replace(key, "");
            }
            provinceShort.put(entry.getKey(), result);
        }

        for (Map.Entry<String, String> entry : AreaData.CITIES.entrySet()) {
            String result = entry.getValue();
            if(result.length() > 2){
                for (String key : AreaData.cityKeys) {
                    result = result.replace(key, "");
                }
                cityShort.put(entry.getKey(), result);
            }
        }

        for (Map.Entry<String, String> entry : AreaData.COUNTIES.entrySet()) {
            String result = entry.getValue();
            if("雨花台区".equals(result)){
                result = "雨花区";
            }
            if(result.length() > 2){
                for (String key : AreaData.countyKeys) {
                    if (result.indexOf(key) > 0) {
                        result = result.replace(key, "");
                    }
                }
                countyShort.put(entry.getKey(), result);
            }
        }
    }

    /**
     * 解析地址,解析结果包含[省,市,区]集合信息,越靠前越符合预期
     *
     * 基本实现原理为: 1.先通过省匹配(parseByProvince),其中逻辑为:先匹配省份名称或者省缩写,根据省份再去匹配城市(parseCityByProvince),地区(parseCountyByCounty),其中parseCityByProvince还包含有parseCountyByCity
     *                 2.通过城市匹配(parseByCity),其中逻辑为:先匹配城市名称或者城市缩写,根据城市在匹配地区(parseCountyByCity)
     *                 3.通过区匹配(parseByCounty),其中逻辑为:匹配地区名称或者缩写,再截取左边字符判断是否有省,市,有则表示精确匹配,没有则表示模糊匹配到,不能确定省,市,后续根据可信度进行判断
     *                 三者从前到后依次执行,如果parseAll = false 则前一个匹配上则不进行下一步匹配,如果parseAll = true,则需要三者都要进行匹配,再经过可信度排序
     *                 排序逻辑为1.是否精确解析到 parse = true
     *                           2.name越短的结果
     * @param address
     * @param parseAll
     * @return
     */
    public static List<ParseAreaResult> parse(String address, boolean parseAll) {
        List<ParseAreaResult> list = new ArrayList<>();
        list.addAll(0, parseByProvince(address));
        if (parseAll || CollectionUtil.isEmpty(list) || !list.get(0).getParse()) {
            list.addAll(0, parseByCity(address));
            if (parseAll || CollectionUtil.isEmpty(list) || !list.get(0).getParse()) {
                list.addAll(0, parseByCounty(address));
            }
        }

        // 可信度排序
        list.sort((a, b) -> {
            int aNameLength = StrUtil.isEmpty(a.getName()) ? -1 : a.getName().length();
            int bNameLength = StrUtil.isEmpty(b.getName()) ? -1 : b.getName().length();
            return a.getParse() && !b.getParse() ? -1 : !a.getParse() && b.getParse() ? 1 : aNameLength > bNameLength ? 1 : aNameLength < bNameLength ? -1 : 0;
        });

        return list;
    }

    /**
     * 通过区解析地址
     * @param addressBase
     * @return
     */
    private static List<ParseAreaResult> parseByCounty(String addressBase) {
        List<ParseAreaResult> results = new ArrayList<>();
        ParseAreaResult result = new ParseAreaResult();
        result.setType("parseByCounty");
        String address = addressBase;
        for (Map.Entry<String, String> entry : AreaData.COUNTIES.entrySet()) {
            String countyCode = entry.getKey();
            String countyName = entry.getValue();
            int index = address.indexOf(countyName);
            String shortCounty = index > -1 ? "" : countyShort.get(countyCode);
            int countyLength = StrUtil.isNotEmpty(shortCounty) ? shortCounty.length() : countyName.length();
            if (StrUtil.isNotEmpty(shortCounty)) {
                index = address.indexOf(shortCounty);
            }

            if (index > -1) {
                if (countyCode.contains("-")) {
                    countyCode = countyCode.split("-")[0];
                }
                result.setCode(countyCode);
                result.setCounty(countyName);
                result.setCountyCode(countyCode);

                String cityCode = countyCode.substring(0, 4) + "00";
                String city = AreaData.CITIES.get(cityCode);
                result.setCityCode(cityCode);
                result.setCity(city);

                String provinceCode = countyCode.substring(0, 2) + "0000";
                String province = AreaData.PROVINCES.get(provinceCode);
                result.setProvinceCode(provinceCode);
                result.setProvince(province);


                String leftAddress = address.substring(0, index);
                String _provinceName = "", _cityName = "";
                if (StrUtil.isNotEmpty(leftAddress)) {
                    _provinceName = province;
                    int _index = leftAddress.indexOf(_provinceName);
                    if (_index == -1) {
                        _provinceName = provinceShort.get(countyCode.substring(0, 2) + "0000");
                        _index = leftAddress.indexOf(_provinceName);
                        if (_index == -1) {
                            _provinceName = "";
                        }
                    }
                    if (StrUtil.isNotEmpty(_provinceName)) {
                        leftAddress = leftAddress.replaceAll(_provinceName, "");
                    }

                    _cityName = city;
                    _index = leftAddress.indexOf(_cityName);
                    if (_index == -1) {
                        _cityName = cityShort.get(countyCode.substring(0, 4) + "00");
                        _index = leftAddress.indexOf(_cityName);
                        if (_index == -1) {
                            _cityName = "";
                        }
                    }
                    if (StrUtil.isNotEmpty(_cityName)) {
                        leftAddress = leftAddress.replaceAll(_cityName, "");
                    }
                    if (StrUtil.isNotEmpty(leftAddress)) {
                        result.setName(leftAddress.trim());
                    }
                }
                address = address.substring(index + countyLength);

                if (StrUtil.isNotEmpty(_provinceName) || StrUtil.isNotEmpty(_cityName)) {
                    result.setParse(true);
                    break;
                } else {
                    //如果没有识别到地区 缓存本次结果，并重置数据
                    ParseAreaResult newResult = new ParseAreaResult();
                    BeanUtil.copyProperties(result, newResult);
                    newResult.setDetails(address.trim());
                    results.add(0, newResult);
                    result.clean();
                    address = addressBase;
                }
            }
        }

        if (StrUtil.isNotEmpty(result.getCode())) {
            result.setDetails(address.trim());
            results.add(0, result);
        }
        return results;
    }

    /**
     * 通过省解析地址
     * @param addressBase
     * @return
     */
    private static List<ParseAreaResult> parseByCity(String addressBase) {
        List<ParseAreaResult> results = new ArrayList<>();
        ParseAreaResult result = new ParseAreaResult();
        result.setType("parseByCity");
        String address = addressBase;
        for (Map.Entry<String, String> entry : AreaData.CITIES.entrySet()) {
            String cityCode = entry.getKey();
            String cityName = entry.getValue();
            int index = address.indexOf(cityName);
            String shortCity = index > -1 ? "" : cityShort.get(cityCode);
            int cityLength = StrUtil.isNotEmpty(shortCity) ? shortCity.length() : cityName.length();
            if (StrUtil.isNotEmpty(shortCity)) {
                index = address.indexOf(shortCity);
            }

            if (index > -1) {
                result.setCode(cityCode);
                result.setCity(cityName);
                result.setCityCode(cityCode);

                String provinceCode = cityCode.substring(0, 2) + "0000";
                String province = AreaData.PROVINCES.get(provinceCode);
                result.setProvinceCode(provinceCode);
                result.setProvince(province);

                String leftAddress = address.substring(0, index);
                String _provinceName = "";
                if (StrUtil.isNotEmpty(leftAddress)) {
                    _provinceName = province;
                    int _index = leftAddress.indexOf(_provinceName);
                    if (_index == -1) {
                        _provinceName = provinceShort.get(cityCode.substring(0, 2) + "0000");
                        _index = leftAddress.indexOf(_provinceName);
                        if (_index == -1) {
                            _provinceName = "";
                        }
                    }
                    if (StrUtil.isNotEmpty(_provinceName)) {
                        leftAddress = leftAddress.replace(_provinceName, "");
                    }
                    if (StrUtil.isNotEmpty(leftAddress)) {
                        result.setName(leftAddress);
                    }
                }

                address = address.substring(index + cityLength);
                address = parseAreaByCity(address, result);
                if (StrUtil.isNotEmpty(_provinceName) || StrUtil.isNotEmpty(result.getCounty())) {
                    result.setParse(true);
                    break;
                } else {
                    //如果没有识别到地区 缓存本次结果，并重置数据
                    ParseAreaResult newResult = new ParseAreaResult();
                    BeanUtil.copyProperties(result, newResult);
                    newResult.setDetails(address.trim());
                    results.add(0, newResult);
                    result.clean();
                    address = addressBase;
                }
            }
        }

        if (StrUtil.isNotEmpty(result.getCode())) {
            result.setDetails(address.trim());
            results.add(0, result);
        }
        return results;
    }

    /**
     * 通过省解析地址
     * @param addressBase
     * @return
     */
    public static List<ParseAreaResult> parseByProvince(String addressBase) {
        List<ParseAreaResult> results = new ArrayList<>();
        ParseAreaResult result = new ParseAreaResult();
        result.setType("parseByProvince");
        String address = addressBase;
        for (Map.Entry<String, String> entry : AreaData.PROVINCES.entrySet()) {
            String code = entry.getKey();
            String province = entry.getValue();
            int index = address.indexOf(province);
            String shortProvince = index > -1 ? "" : provinceShort.get(code);
            int provinceLength = StrUtil.isNotEmpty(shortProvince) ? shortProvince.length() : province.length();
            if (StrUtil.isNotEmpty(shortProvince)) {
                index = address.indexOf(shortProvince);
            }
            if (index > -1) {
                if (index > 0) {
                    result.setName(address.substring(0, index).trim());
                    address = address.substring(index).trim();
                }
                result.setCode(code);
                result.setProvince(province);
                result.setProvinceCode(code);

                String _address = address.substring(provinceLength);
                if (!"市".equals(_address.charAt(0)) || _address.indexOf(province) > -1) {
                    address = _address;
                }
                //如果是用短名匹配的 要替换省关键字
                if (StrUtil.isNotEmpty(shortProvince)) {
                    for (String key : AreaData.provinceKeys) {
                        if (address.indexOf(key) == 0) {
                            address = address.substring(key.length());
                        }
                    }
                }
                String __address = parseCityByProvince(address, result);
                if (StrUtil.isEmpty(result.getCity())) {
                    __address = parseCountyByProvince(address, result);
                }

                if (StrUtil.isNotEmpty(result.getCity())) {
                    address = __address;
                    result.setParse(true);
                    break;
                } else {
                    //如果没有识别到地区 缓存本次结果，并重置数据
                    ParseAreaResult newResult = new ParseAreaResult();
                    BeanUtil.copyProperties(result, newResult);
                    newResult.setDetails(address.trim());
                    results.add(0, newResult);
                    result.clean();
                    address = addressBase;
                }
            }
        }

        //设置code
        if (StrUtil.isNotEmpty(result.getCode())) {
            result.setDetails(address.trim());
            results.add(0, result);
        }
        return results;
    }

    /**
     * 通过省解析地区信息
     * @param address
     * @param result
     * @return
     */
    private static String parseCountyByProvince(String address, ParseAreaResult result) {
        Map<String, String> counties = AreaUtils.getTargetsByCode(AreaEnum.COUNTY, result.getCode());
        for (Map.Entry<String, String> entry : counties.entrySet()) {
            String countyCode = entry.getKey();
            String countyName = entry.getValue();
            int index = address.indexOf(countyName);
            String shortCounty = index > -1 ? "" : countyShort.get(countyCode);
            int countyLength = StrUtil.isNotEmpty(shortCounty) ? shortCounty.length() : countyName.length();
            if (StrUtil.isNotEmpty(shortCounty)) {
                index = address.indexOf(shortCounty);
            }
            if (index > -1 && index < 6) {
                if (countyCode.contains("-")) {
                    countyCode = countyCode.split("-")[0];
                }
                result.setCode(countyCode);
                result.setCounty(countyName);
                result.setCountyCode(countyCode);

                String cityCode = countyCode.substring(0, 4) + "00";
                String cityName = AreaData.CITIES.get(cityCode);
                result.setCity(cityName);
                result.setCityCode(cityCode);

                address = address.substring(index + countyLength);

                if (StrUtil.isNotEmpty(shortCounty)) {
                    for (String key : AreaData.countyKeys) {
                        if (address.indexOf(key) == 0) {
                            address = address.substring(key.length());
                        }
                    }
                }
                break;
            }
        }
        return address;
    }

    /**
     * 通过省解析城市信息
     * @param address
     * @param result
     * @return
     */
    private static String parseCityByProvince(String address, ParseAreaResult result) {
        Map<String, String> cities = AreaUtils.getTargetsByCode(AreaEnum.CITY, result.getCode());
        for (Map.Entry<String, String> entry : cities.entrySet()) {
            String cityCode = entry.getKey();
            String cityName = entry.getValue();
            int index = address.indexOf(cityName);
            String shortCity = index > -1 ? "" : cityShort.get(cityCode);
            int cityLength = StrUtil.isNotEmpty(shortCity) ? shortCity.length() : cityName.length();
            if (StrUtil.isNotEmpty(shortCity)) {
                index = address.indexOf(shortCity);
            }
            if (index > -1 && index < 3) {
                result.setCode(cityCode);
                result.setCity(cityName);
                result.setCityCode(cityCode);
                address = address.substring(index + cityLength);
                //如果是用短名匹配的 要替换市关键字
                if (StrUtil.isNotEmpty(shortCity)) {
                    String finalAddress = address;
                    for (String key : AreaData.cityKeys) {
                        if (address.indexOf(key) == 0 && !StrUtil.equals(key, "市")) {
                            //排除几个会导致异常的解析
                            boolean anyMatch = Stream.of("市北区", "市南区", "市中区", "市辖区").anyMatch(v -> finalAddress.indexOf(v) == 0);
                            if (!anyMatch) {
                                address = address.substring(key.length());
                            }
                        }
                    }
                }
                address = ParseArea.parseAreaByCity(address, result);
                break;
            }
        }
        return address;
    }

    /**
     * 通过城市解析地区信息
     * @param address
     * @param result
     * @return
     */
    private static String parseAreaByCity(String address, ParseAreaResult result) {
        Map<String, String> counties = AreaUtils.getTargetsByCode(AreaEnum.COUNTY, result.getCode());
        for (Map.Entry<String, String> entry : counties.entrySet()) {
            String countyCode = entry.getKey();
            String countyName = entry.getValue();
            int index = address.indexOf(countyName);
            String shortCounty = index > -1 ? "" : countyShort.get(countyCode);
            int countyLength = StrUtil.isNotEmpty(shortCounty) ? shortCounty.length() : countyName.length();
            if (StrUtil.isNotEmpty(shortCounty)) {
                index = address.indexOf(shortCounty);
            }
            if (index > -1 && index < 3) {
                if (countyCode.contains("-")) {
                    countyCode = countyCode.split("-")[0];
                }
                result.setCode(countyCode);
                result.setCounty(countyName);
                result.setCountyCode(countyCode);

                address = address.substring(index + countyLength);
                if (StrUtil.isNotEmpty(shortCounty)) {
                    for (String key : AreaData.countyKeys) {
                        if (address.indexOf(key) == 0) {
                            address = address.substring(key.length());
                        }
                    }
                }
                break;
            }
        }
        return address;
    }
}
